Imports¶

In [1]:
import pickle
import plotly.express as px
import dash
from dash import dcc, html, Input, Output
import plotly.graph_objects as go
from plotly.subplots import make_subplots
import numpy as np
import pandas as pd
import numpy as np
import pandas as pd
import seaborn as sns
import matplotlib.pyplot as plt
import plotly
import plotly.colors
import re
import plotly.figure_factory as ff
import warnings

# Suppress FutureWarning messages
warnings.simplefilter(action='ignore', category=FutureWarning)

Distribution By Section¶

Functions¶

In [2]:
def get_continuous_color(colorscale, intermed):
    """
    Plotly continuous colorscales assign colors to the range [0, 1]. This function computes the intermediate
    color for any value in that range.

    Plotly doesn't make the colorscales directly accessible in a common format.
    Some are ready to use:
    
        colorscale = plotly.colors.PLOTLY_SCALES["Greens"]

    Others are just swatches that need to be constructed into a colorscale:

        viridis_colors, scale = plotly.colors.convert_colors_to_same_type(plotly.colors.sequential.Viridis)
        colorscale = plotly.colors.make_colorscale(viridis_colors, scale=scale)

    :param colorscale: A plotly continuous colorscale defined with RGB string colors.
    :param intermed: value in the range [0, 1]
    :return: color in rgb string format
    :rtype: str
    """
    if len(colorscale) < 1:
        raise ValueError("colorscale must have at least one color")

    if intermed <= 0 or len(colorscale) == 1:
        return colorscale[0][1]
    if intermed >= 1:
        return colorscale[-1][1]

    for cutoff, color in colorscale:
        if intermed > cutoff:
            low_cutoff, low_color = cutoff, color
        else:
            high_cutoff, high_color = cutoff, color
            break

    # noinspection PyUnboundLocalVariable
    return plotly.colors.find_intermediate_color(
        lowcolor=low_color, highcolor=high_color,
        intermed=((intermed - low_cutoff) / (high_cutoff - low_cutoff)),
        colortype="rgb")

def convert_rgb_to_int(rgb_string):
    # Use a regular expression to find all floating point numbers in the string
    numbers = re.findall(r'\d+\.\d+', rgb_string)
    
    # Convert each number to an integer
    int_numbers = [str(int(float(num))) for num in numbers]
    
    # Replace the original floating point numbers with the converted integers in the string
    for num, int_num in zip(numbers, int_numbers):
        rgb_string = rgb_string.replace(num, int_num)
    
    return rgb_string

def format_colorscale(colorscale):
    formatted_colorscale = []
    for value, color in colorscale:
        # Format the float to two decimal places
        formatted_value = float(f"{value:.2f}")
        
        # Use regular expressions to find the numbers in the color string and convert them to integers
        formatted_color = re.sub(r'\d+\.\d+', lambda x: str(int(float(x.group()))), color)
        
        # Append the formatted value and color to the new colorscale list
        formatted_colorscale.append([formatted_value, formatted_color])
    
    return formatted_colorscale

Preprocess Data¶

In [3]:
with open('Data/key_distribution', 'rb') as f:
    data = pickle.load(f)
  
data = data * 20000
cks = pd.Series([list(data[let]) for let in data.columns], index = data.columns, dtype = object)
data = data.transpose()

display(data.head())

# Calculate the frequency distribution for each key across the left, center, and right sections
extremes = data.iloc[:, [0, 1, 2, 7, 8, 9, 20, 21, 22, 27, 28, 29]].sum(axis=1)
home_row = data.iloc[:, [10, 11, 12, 13, 16, 17, 18, 19]].sum(axis=1)
center = data.iloc[:, [3, 4, 5, 6, 14, 15, 23, 24, 25, 26]].sum(axis=1)

columns = ['extremes', 'home_row', 'center']

# Combine the results into a single DataFrame
frequency_distribution_sections = pd.DataFrame({
    columns[0]: eval(columns[0]),
    columns[1]: eval(columns[1]),
    columns[2]: eval(columns[2]),
})

frequency_distribution_sections.reset_index(inplace=True)
frequency_distribution_sections = frequency_distribution_sections.melt(id_vars=["index"], var_name="Section", value_name="Frequency")

pvt = frequency_distribution_sections.pivot("index", "Section", "Frequency")
pvt = pvt[columns]
pvt = pvt.iloc[4:].append(pvt.iloc[:4])

pvt = (pvt/1026).round(3) * 100
n = np.array(pvt)
0 1 2 3 4 5 6 7 8 9 ... 20 21 22 23 24 25 26 27 28 29
q 130.0 1.0 0.0 0.0 0.0 1.0 0.0 0.0 0.0 104.0 ... 388.0 1.0 0.0 0.0 6.0 0.0 0.0 0.0 2.0 393.0
w 0.0 37.0 31.0 0.0 331.0 0.0 0.0 37.0 44.0 0.0 ... 0.0 1.0 215.0 33.0 0.0 50.0 41.0 206.0 0.0 0.0
e 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 ... 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0
r 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 ... 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0
t 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 ... 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0

5 rows × 30 columns

Visualization¶

In [4]:
viridis_colors, _ = plotly.colors.convert_colors_to_same_type(plotly.colors.sequential.Reds)
colorscale = plotly.colors.make_colorscale(viridis_colors)

get_continuous_color(colorscale, 1)
colors = []

colors.append([0.00, 'rgb(255, 255, 255)'])

colors.append([0.22, 'rgb(255, 255, 255)'])
colors.append([0.46, 'rgb(254, 254, 254)'])

viridis_colors, _ = plotly.colors.convert_colors_to_same_type(plotly.colors.sequential.Purples)
colorscale = plotly.colors.make_colorscale(viridis_colors)

# 16
for i in range(1, 13):
    val = (44 + i * 2 - 45)
    
    cl = get_continuous_color(colorscale, val/65)
    colors.append([(val + 45) * 0.01,convert_rgb_to_int(cl)])
    
colors.append([1, (get_continuous_color(colorscale, 55/80))])
colors = format_colorscale(colors)

z = pvt.values

# Create text matrix of the same dimension as z
text = [[str("") for value in row] for row in z]


fig = ff.create_annotated_heatmap(n[::-1], 
                                  x=list(pvt.columns), y= list(reversed(list(pvt.index))), 
                                  colorscale=colors, showscale=True, font_colors = ['black'], zmin = 0, zmax = 100)                  

# Update layout to ensure x-axis labels are at the bottom
fig.update_layout(
    title="Frequency Distribution of Keys Across Keyboard Sections",
    yaxis_title="Key",
    
    margin=dict(l=0, r=0, t=60, b=10)  # Adjust margins to remove whitespace
)



fig.update_traces(
    hoverinfo='text',
    hovertemplate='%{text}<extra></extra>',
    text=text
)


text = [[f"{val:.2f}" for val in row] for row in n[::-1]]

# Update traces to use the formatted text annotations
for i in range(len(fig.layout.annotations)):
    fig.layout.annotations[i].text = text[i // len(n[0])][i % len(n[0])]

fig.show()

Distribution By Row¶

Preprocessing¶

In [5]:
# Calculate the frequency distribution for each key across the left, center, and right sections
top = data.iloc[:, [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]].sum(axis=1)
bottom = data.iloc[:, [20, 21, 22, 23, 24, 25, 26, 27, 28, 29]].sum(axis=1)

columns = ['top', 'bottom']

# Combine the results into a single DataFrame
frequency_distribution_sections = pd.DataFrame({
    columns[0]: eval(columns[0]),
    columns[1]: eval(columns[1]),
})

frequency_distribution_sections.reset_index(inplace=True)
frequency_distribution_sections = frequency_distribution_sections.melt(id_vars=["index"], var_name="Section", value_name="Frequency")

pvt = frequency_distribution_sections.pivot("index", "Section", "Frequency")
pvt = pvt[columns]
pvt = pvt.iloc[4:].append(pvt.iloc[:4])

pvt = (pvt/1026).round(3) * 100
n = np.array(pvt)

Visualization¶

In [6]:
colors = []

colors.append([0.00, 'rgb(255, 255, 255)'])

colors.append([0.22, 'rgb(255, 255, 255)'])
colors.append([0.60, 'rgb(254, 254, 254)'])
viridis_colors, _ = plotly.colors.convert_colors_to_same_type(plotly.colors.sequential.Purples)
colorscale = plotly.colors.make_colorscale(viridis_colors)

# 16
for i in range(1, 10):
    val = (60 + i * 2 - 60)
    
    cl = get_continuous_color(colorscale, val/40)
    colors.append([(val + 60) * 0.01,convert_rgb_to_int(cl)])
    
colors.append([1, (get_continuous_color(colorscale, 55/80))])
colors = format_colorscale(colors)

z = pvt.values

# Create text matrix of the same dimension as z
text = [[str("") for value in row] for row in z]


fig = ff.create_annotated_heatmap(n[::-1], 
                                  x=list(pvt.columns), y= list(reversed(list(pvt.index))), 
                                  colorscale=colors, showscale=True, font_colors = ['black'], zmin = 0, zmax = 100)                  

# Update layout to ensure x-axis labels are at the bottom
fig.update_layout(
    title="Frequency Distribution of Keys For Top and Bottom Rows",
    yaxis_title="Key",
    
    margin=dict(l=0, r=0, t=60, b=10)  # Adjust margins to remove whitespace
)



fig.update_traces(
    hoverinfo='text',
    hovertemplate='%{text}<extra></extra>',
    text=text
)


text = [[f"{val:.2f}" for val in row] for row in n[::-1]]

# Update traces to use the formatted text annotations
for i in range(len(fig.layout.annotations)):
    fig.layout.annotations[i].text = text[i // len(n[0])][i % len(n[0])]

fig.show()

3D Clicks Extra Chart¶

In [7]:
file = 'Data/war_and_peace_text.txt'

lines = []

# Open the file and read line by line
with open(file, 'r') as file:
    for line in file:
        # Strip the newline character at the end of each line
        s = line.split(':')
        let = s[0].strip()
        val = int(s[1].strip())
        
        lines.append([let, val])

# Convert the list of lines into a DataFrame
#lets = pd.DataFrame(lines, columns=['Line'])
#lets
In [8]:
df = pd.DataFrame(lines)
df = df.set_index(0)
df.columns = ['percs']
df['percs'] = df['percs'] / df['percs'].sum() * 100
In [9]:
import numpy as np
import plotly.graph_objs as go
from plotly.subplots import make_subplots

# Define the towers function
def towers(a, e, pos_x, pos_y, color, text):
    # create points
    x, y, z = np.meshgrid(
        np.linspace(pos_x - a / 2, pos_x + a / 2, 2), 
        np.linspace(pos_y - a / 2, pos_y + a / 2, 2), 
        np.linspace(0, e, 2)
    )
    x = x.flatten()
    y = y.flatten()
    z = z.flatten()
    
    return go.Mesh3d(x=x, y=y, z=z, alphahull=1, flatshading=True, color=color, hovertext=text, hoverinfo='text')

# Modify the get_keyboard function to return the traces
def get_keyboard(keys, df):
    best_list = [list(keys[i:i+10]) for i in range(0, len(keys), 10)]
    l = []
    for b in best_list:
        for i in b:
            l.append(i)
    
    vals = []
    for i in l:
        try:
            vals.append(df.loc[i]['percs'])
        except:
            vals.append(0.01)  # Default value if key is not found
    
    # dimensions of global grid
    x_dim = 10
    y_dim = 3

    x, y = np.meshgrid(
        np.arange(x_dim),
        np.arange(y_dim)
    )
    x = x[:, ::-1]

    xx = x.flatten()
    yy = y.flatten()

    # Normalize z values to get shades of gray
    min_val = min(vals)
    max_val = max(vals)
    normalized_vals = [(v - min_val) / (max_val - min_val) * 255 for v in vals]

    # Grayscale colors based on z values
    colors = [f'rgba({int(v)}, {int(v)}, {int(v)}, 1)' for v in normalized_vals]

    traces = []
    for i, (x, y, z) in enumerate(zip(xx, yy, vals)):
        traces.append(towers(1, z, x, y, colors[i], l[i]))
        
    return traces


# Example usage
keys1 = "qwertyuiopasdfghjkl;zxcvbnm,./"
keys2 = "/.xsglv,mjetcpnohariq;yfbkduwz"


# Assuming df is a DataFrame with the necessary data
# Create traces for two different key configurations
traces1 = get_keyboard(keys1, df)
traces2 = get_keyboard(keys2, df)

# Create subplots
fig = make_subplots(rows=1, cols=2, specs=[[{'is_3d': True}, {'is_3d': True}]], 
                    subplot_titles=("QWERTY", "Optimal Keyboard")
)

for trace in traces2:
    fig.add_trace(trace, row=1, col=2)

for trace in traces1:
    fig.add_trace(trace, row=1, col=1)


    
# Define the camera view for the first subplot
camera1 = dict(
    up=dict(x=0, y=0, z=1),
    center=dict(x=0, y=0, z=0),
    eye=dict(x=1.2, y=2.2, z=.9)
)

# Define the camera view for the second subplot with slight adjustments
camera2 = dict(
    up=dict(x=0, y=0, z=1),
    center=dict(x=0, y=0, z=0),
    eye=dict(x=1.6, y=1.6, z=1.6)
)
    
fig.update_layout(
    scene=dict(
        domain = dict(x = [0, 0.5]),
        bgcolor='rgba(255, 255, 255, 1)',  # White background for the first subplot
        xaxis=dict(showbackground=True, showgrid=True, showticklabels=False, title=''),
        yaxis=dict(showbackground=True, showgrid=True, showticklabels=False, title=''),
        zaxis=dict(showbackground=True, showgrid=True, showticklabels=False, title=''),

        camera=camera1  # Apply the camera configuration for the first subplot
    ),
    scene2=dict(
        domain = dict(x = [.5, 1]),
        bgcolor='rgba(255, 255, 255, 1)',  # Light grey background for the second subplot
        xaxis=dict(showbackground=True, showgrid=True, showticklabels=False, title=''),
        yaxis=dict(showbackground=True, showgrid=True, showticklabels=False, title=''),
        zaxis=dict(showbackground=True, showgrid=True, showticklabels=False, title=''),

        camera=camera1  # Apply the camera configuration for the second subplot
    ),
    margin=dict(l=0, r=0, t=20, b=0)
)

for i in range(len(fig['layout']['annotations'])):
    fig['layout']['annotations'][i]['font'] = dict(size=25, color='black')
    fig['layout']['annotations'][i]['y'] = fig['layout']['annotations'][i]['y'] - 0.05  # Adjust y to reduce space
In [10]:
fig

Clicks Distribution¶

Functions¶

In [11]:
def clicks_to_colors(clicks):
    """
    Convert click values to a list of colors using the OrRd color scale. 
    Values between 1 and 25 are mapped to the color gradient. Values of 0 are mapped to white.
    """
    
    # Mint
    # Dense
    # Burg
    colorscale = px.colors.sequential.Mint
    max_click = 440
    min_click = 50
    colors_list = []

    for click in clicks:
        if click > max_click:
            colors_list.append(colorscale[-1])
        elif click == 0:
            colors_list.append("rgba(255, 255, 255, 1)")  # White for 0 clicks
        else:
            # Normalize the click value between min_click and max_click
            normalized_click = (click+0.8) / (max_click+0.8)
            # Map normalized click to a color in the colorscale
            color_idx = int(normalized_click * (len(colorscale) - 1))
            colors_list.append(colorscale[color_idx])
    
    return colors_list

# Function to get the coordinates of a letter on a QWERTY keyboard
def get_qwerty_coordinates(letter):
    for y, row in enumerate(qwerty_layout):
        if letter in row:
            x = row.index(letter)
            
            if y == 0:
                return x, y
            elif y == 1:
                return x + 0.25, y
            else:
                return x + 0.75, y
    return None


# Function to create a box (rectangle) trace with optional annotation
def create_box(x0, y0, x1, y1, color, letter=None):
    return go.Scatter(
        x=[x0, x1, x1, x0, x0],  # Coordinates for the rectangle
        y=[y0, y0, y1, y1, y0],  # Coordinates for the rectangle
        fill='toself',  # Fill the box
        fillcolor=color,  # Color to fill
        line=dict(color='black'),  # Border color
        mode='lines+text' if letter else 'lines',  # Mode for the plot
        text=[letter] if letter else None,  # The letter annotation
        textposition='bottom left',
        showlegend=False,
        hoverinfo='none',  # Hide trace info
        hovertemplate=f'%<extra></extra>',  # Custom hovertemplate

    )

# Function to generate the keyboard layout with offsets
def generate_keyboard_boxes(colors, selected_letter=None):
    x = np.arange(10)  # X positions for the keys
    y = np.array([2, 1, 0])  # Y positions for the rows
    offsets = [0, 0.25, 0.75]  # Offsets for the rows

    boxes = []

    color_idx = 0  # Color index
    for i in range(len(y)):  # For each row
        for j in range(len(x)):  # For each key in the row
            x0 = x[j] + offsets[i]  # Calculate x0 with offset
            y0 = y[i]  # y0 position
            x1 = x0 + 1  # x1 position
            y1 = y0 + 1  # y1 position
            letter = None
            if selected_letter and cks[f'{selected_letter}'] == clicks_to_colors(cks[f'{selected_letter}'])[i * len(x) + j]:
                letter = selected_letter.upper()
            boxes.append(create_box(x0, y0, x1, y1, colors[color_idx % len(colors)], letter))  # Create and append the box with optional letter
            
            color_idx += 1  # Increment color index
    
    return boxes

Visualization¶

In [12]:
# Define the QWERTY keyboard layout
qwerty_layout = [
    ['q', 'w', 'e', 'r', 't', 'y', 'u', 'i', 'o', 'p'],
    ['a', 's', 'd', 'f', 'g', 'h', 'j', 'k', 'l', ';'],
    ['z', 'x', 'c', 'v', 'b', 'n', 'm', ',', '.', '/']
]



# Get default colors for the initial display
default_colors = clicks_to_colors(cks['q'])

letter_buttons = [{
    'label': f'{a}',
    'method': 'update',
    'args': [
        {'fillcolor': clicks_to_colors(cks[f'{a}'])},
        {'title': f'Key Distribution For',
         'annotations': [
             dict(
                 x=(get_qwerty_coordinates(f'{a}')[0] + 0.5),  # Center x position of the box
                 y=(2 - get_qwerty_coordinates(f'{a}')[1] + 0.5),  # Center y position of the box, assuming y positions are reversed
                 text=f'{a}'.upper(),  # The letter in uppercase
                 showarrow=False,
                 font=dict(size=23, color='black')
             )
         ]
        }
    ]
} for a in data.index]

# Initial boxes with default color
boxes = generate_keyboard_boxes(default_colors, 'q')

boxes[0] = go.Scatter({
    'fill': 'toself',
    'fillcolor': 'rgb(137, 192, 182)',
    'line': {'color': 'black'},
    'mode': 'lines+text',
    'showlegend': False,
    'x': [0, 1, 1, 0, 0],
    'y': [2, 2, 3, 3, 2]
})



# Create the figure
fig = go.Figure()

 
# Add the initial boxes to the figure
for box in boxes:
    fig.add_trace(box)
    
    # Define the manual position for the text
text_x = 0.5  # Middle of the box in the x-axis
text_y = 2.5  # Middle of the box in the y-axis

# Add annotation for the text
fig.add_annotation(
    x=text_x,
    y=text_y,
    text="Q",
    showarrow=False,
    font=dict(size=28),
    xanchor='center',
    yanchor='middle'
)


# Dropdown menu for color selection
updatemenus = [
    {
        'buttons': letter_buttons,  # Attach the letter buttons
        'direction': 'down',  # Dropdown direction
        'showactive': True,  # Show the active button
        'x': 0.365,  # x position of the dropdown
        'xanchor': 'center',  # x anchor position
        'y': 1.025,  # y position of the dropdown
        'yanchor': 'top',  # y anchor position
        'font': dict(size=20),  # Set the font size for the dropdown

    }
]

# Update the layout to include the dropdown menu
fig.update_layout(
    title=dict(
        text='Key Distribution For',
        font=dict(size=33),  # Set the font size for the title
        y=0.93,  # Adjust the vertical position of the title
        x=.15,  # Center the title horizontally
        xanchor='center',
        yanchor='top'
    ),
    updatemenus=updatemenus,
    xaxis=dict(showgrid=False, zeroline=False, visible=False),
    yaxis=dict(showgrid=False, zeroline=False, visible=False),
    width=1000,
    height=400,
    plot_bgcolor='white',
)




# Create a dummy heatmap for the colorscale legend
fig.add_trace(go.Heatmap(
    z=[[400, 2200]],  # Dummy data for the heatmap
    colorscale='Mint',  # Choose a colorscale
    showscale=True,  # Show the scale
    colorbar=dict(
        orientation='v',
        x=1.05,
        y=0.93,
        thickness=15,
        len=0.8,
        xanchor='center',
        yanchor='top',
        tickvals=[400, 1000, 1600, 2200],  # Custom tick values
        ticktext=['4%', '10%', '16%', '24%']
        
    ),
    opacity = 0,
    hoverinfo='none'
))


# Update layout to ensure x-axis labels are at the bottom
fig.update_layout(
    
    margin=dict(l=0, r=0, t=60, b=0)  # Adjust margins to remove whitespace
)

# Show the plot
fig.show()

How Varied Is Each Key¶

In [13]:
def calculate_entropy(distribution):
    # Normalize the distribution to get probabilities
    probabilities = distribution / np.sum(distribution)
    # Calculate entropy
    entropy = -np.sum(probabilities * np.log(probabilities + 1e-9))  # adding a small value to avoid log(0)
    return entropy



# Calculate entropy for each letter
entropies = {letter: calculate_entropy(np.array(distribution)) for letter, distribution in cks.items()}

# Normalize the entropy values to a range of [0, 1]
max_entropy = np.log(30)  # The maximum possible entropy for 30 spots (uniform distribution)
normalized_entropies = {letter: entropy / max_entropy for letter, entropy in entropies.items()}

# Output the results
k = dict(sorted(normalized_entropies.items(),key=lambda x:x[1],reverse = True))
k
Out[13]:
{'w': 0.5552318618446004,
 ',': 0.5352788755955837,
 'b': 0.53373853790098,
 'j': 0.5213352305879383,
 'v': 0.5056730147802252,
 'a': 0.5047236757513366,
 'y': 0.5025116716131943,
 'x': 0.4852808896906422,
 's': 0.48474979069741186,
 '.': 0.4667051991966148,
 ';': 0.4341400190372003,
 'o': 0.43249029300144104,
 'f': 0.4295995673178524,
 'i': 0.4267866063560094,
 'g': 0.40851724057716315,
 '/': 0.4000346479544242,
 'z': 0.3968369990218024,
 't': 0.39241642404082283,
 'q': 0.37974866881154307,
 'l': 0.35375172180049774,
 'k': 0.3532568500324857,
 'e': 0.34887196668196946,
 'c': 0.34325037032251576,
 'p': 0.3289777996656306,
 'h': 0.3287734385898439,
 'd': 0.3284891025429945,
 'r': 0.31808728566153666,
 'm': 0.3091655062752482,
 'u': 0.20379281208392103,
 'n': 0.15276060478762374}
In [ ]:
 

Frequencies¶

In [14]:
# Columns come first, Index come second
data = pd.read_csv('Data/times_pressed.csv', index_col=0)
data = data.iloc[:, 1:]
data = data.transpose()
data.head()
Out[14]:
q w e r t y u i o ... z x c v b n m , . /
q 195056067 308002 152266 124260298 3610747 785022 213865 1444774 38816988 2199836 ... 221018 822865 11418816 111577 1072450 9125100 411526 72254 312015 500564
w 4638390461 586402 34394960 438822834 46903733 226215572 23514033 2489845 4549773 1125725016 ... 834072 766541 1841061 558799 2809747 18837412 5533258 821324 5122126 12157861
e 2142108028 483314 1200604260 1269199466 5627498048 3603683062 377423552 424930068 1134528986 129457623 ... 166245727 60045779 1858115400 2693870666 1680561415 2255840336 2468704447 680736 9213826 4635570
r 2441597601 712697 98351248 5990121323 331049184 1227590455 28950845 1928762782 942738458 4110770057 ... 924808 782400 509886799 5404881 375442750 25686685 26237299 490788 4840019 4562133
t 12164315875 712811 18572520 1428979217 1152100849 565565952 64983155 1346985955 3480571544 1264281278 ... 985324 139069618 1228433831 1901068 32002988 2989255817 8619859 1312797 10339044 6434917

5 rows × 31 columns

In [15]:
sums = pd.DataFrame(index = data.index, columns = ['freq'])
sums['freq'] = [data.loc[d].sum() for d in data.index]
sums = sums.sort_values(by = 'freq')/sums.sum() * 100

colemak = ['a', 'r', 's', 't', 'd', 'h', 'n', 'e', 'i', 'o']
dvorak = ['a', 'o', 'e', 'u', 'i', 'd', 'h', 't', 'n', 's']
qwerty = ['a', 's', 'd', 'f', 'g', 'h', 'j', 'k', 'l', ';']
alphabet = ['k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't']
ours = ['i', 'e', 't', 'r', 'h', 'd', 'n', 'o', 's', 'a']

vals = [colemak, dvorak, qwerty, alphabet, ours]
for v in vals:
    print(sums.loc[v].sum().freq)
70.80162991597203
67.48919934452486
32.01422440496837
45.53830235381878
70.80162991597203

Key Removed Scores¶

Functions¶

In [16]:
def clicks_to_colors3(clicks, min, max):
    
    """
    Convert click values to a list of colors using the OrRd color scale. 
    Values between 1 and 25 are mapped to the color gradient. Values of 0 are mapped to white.
    """
    
    # Mint
    # Dense
    # Burg
    colorscale = px.colors.sequential.Mint
    # Create a custom continuous color scale for shades of blue
    colorscale = [
    "#ffffff",
    "#f0f0f0",
    "#e1e1e1",
    "#d2d2d2",
    "#c3c3c3",
    "#b5b5b5",
    "#a7a7a7",
    "#999999",
    "#8b8b8b",
    "#7e7e7e",
    "#707070",
    "#636363",
    "#575757",
    "#4a4a4a",
    "#3e3e3e",
    "#333333",
    "#272727",
    "#1d1d1d",
    "#121212",
    "#000000"
]

    max_click = max
    min_click = min
    colors_list = []
    vals = []

    for click in clicks:
        
        if click < min:
            colors_list.append("#ffffff")
            continue
        
        # Normalize the click value between min_click and max_click
        normalized_click = (click - min_click) / (max_click - min_click)
        # Map normalized click to a color in the colorscale
        color_idx = int(normalized_click * (len(colorscale) - 1))
        #print(normalized_click, color_idx)
        vals.append(color_idx)
        colors_list.append(colorscale[color_idx])
    
    return colors_list, vals


letters = list(data.index)
# Function to generate the keyboard layout with offsets
def generate_keyboard_boxes3(colors, selected_letter=None):
    
    x = np.arange(10)  # X positions for the keys
    y = np.array([2, 1, 0])  # Y positions for the rows
    offsets = [0, 0.25, 0.75]  # Offsets for the rows

    boxes = []

    color_idx = 0  # Color index
    for i in range(len(y)):  # For each row
        for j in range(len(x)):  # For each key in the row
            x0 = x[j] + offsets[i]  # Calculate x0 with offset
            y0 = y[i]  # y0 position
            x1 = x0 + 1  # x1 position
            y1 = y0 + 1  # y1 position
            #letter = letters[0]
            #letter = round(s1[color_idx], 2)
            letter = None
            boxes.append(create_box(x0, y0, x1, y1, colors[color_idx % len(colors)], letter))  # Create and append the box with optional letter
            
            color_idx += 1  # Increment color index
    
    return boxes

colorscale = [
    "#ffffff",
    "#f0f0f0",
    "#e1e1e1",
    "#d2d2d2",
    "#c3c3c3",
    "#b5b5b5",
    "#a7a7a7",
    "#999999",
    "#8b8b8b",
    "#7e7e7e",
    "#707070",
    "#636363",
    "#575757",
    "#4a4a4a",
    "#3e3e3e",
    "#333333",
    "#272727",
    "#1d1d1d",
    "#121212",
    "#000000"
]

Visualization¶

In [17]:
scores = [1.000086,1.000705,1.200568,1.043105,1.131275,1.003043,1.011047,1.084953,1.108289,1.01305,1,1,1.063747,1.104655,1.005745,1.002861,1.162576,1.144321,1.06597,1.172852,1.000114,1,1.004235,1.00383,1.005447,1.071739,1.001518,1.000022,1.000899,1]
letter = qwerty_layout[0] + qwerty_layout[1] + qwerty_layout[2] 
In [18]:
# Create the figure
default_colors, vals = clicks_to_colors3(scores, min(scores), max(scores))
boxes = generate_keyboard_boxes3(default_colors)
fig = go.Figure()

# Add the initial boxes to the figure
for box in boxes:
    fig.add_trace(box)
    
# Define the manual position for the text
text_x = 0.5  # Middle of the box in the x-axis
text_y = 2.5  # Middle of the box in the y-axis
offsets = [.5, .75, 1.25]

idx = 0
for row in range(3):
    for column in range(10):
        
        x = offsets[row] + text_x * column * 2
        y = text_y - row
        
            
        # Add annotation for the text
        fig.add_annotation(
            x=x,
            y=y,
            text = letter[idx].upper(),
            showarrow=False,
            font=dict(size=40),
            xanchor='center',
            yanchor='middle'
        )

        idx += 1
        
# Update the layout to include the dropdown menu
fig.update_layout(
    title=dict(
        font=dict(size=40, color='black'),  # Set the font size and color for the title
        y=0.95,  # Adjust the vertical position of the title
        x=0.24,  # Center the title horizontally
        xanchor='center',
        yanchor='top'
    ),
    title_font_color='black',  # Set the title font color to black

    
    xaxis=dict(showgrid=False, zeroline=False, visible=False),
    yaxis=dict(showgrid=False, zeroline=False, visible=False),
    width=1000,
    height=500,
    plot_bgcolor='white',
)

# Update layout to ensure x-axis labels are at the bottom
fig.update_layout(
    
    margin=dict(l=10, r=10, t=10, b=0)  # Adjust margins to remove whitespace
)

fig

# Create a dummy heatmap for the colorscale legend
fig.add_trace(go.Heatmap(
    z=[[0, 100]],  # Dummy data for the heatmap
    colorscale=colorscale,  # Choose a colorscale
    showscale=True,  # Show the scale
    colorbar=dict(
        orientation='v',
        x=1.05,
        y=0.93,
        thickness=15,
        len=0.8,
        xanchor='center',
        yanchor='top',
        tickvals=[0, 25, 50, 75, 100],  # Custom tick values
        ticktext=['0%', '5%', '10%', '15%', '20%']
        
    ),
    opacity = 0,
    hoverinfo='none'
))
        
fig
In [19]:
sc = pd.DataFrame(scores, index = letters)
sc.describe()
Out[19]:
0
count 30.000000
mean 1.046888
std 0.062799
min 1.000000
25% 1.000754
50% 1.005596
75% 1.081650
max 1.200568